package pcloud.org.server.component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;

import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Files;
import org.nutz.lang.Lang;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.At;

import pcloud.org.server.identity.AuthManager;
import pcloud.org.server.identity.User;
import pcloud.org.server.model.FileInfo;
import pcloud.org.server.resource.FilesInfoResource;
import pcloud.org.server.resource.FilesResource;

@At(FileComponent.PATH)
@IocBean
public class FileComponent
{
	private static final String PATH = "/files";
	private static final Log log = Logs.getLog(FileComponent.class);
	private static FileComponent fileComponent = null;

	private FileComponent()
	{
	}

	public static FileComponent getInstance()
	{
		if (fileComponent == null) {
			fileComponent = new FileComponent();
		}
		return fileComponent;
	}

	public FileInfo get(String publicIdentifier)
	{
		FileInfo publicInfo = new FileInfo(publicIdentifier, getRootPath(publicIdentifier), true);
		log.debug("request file info : '" + Json.toJson(publicInfo) + "'");

		FileInfo privateInfo = getPrivateInfo(publicInfo);
		log.debug("private file info : '" + Json.toJson(privateInfo) + "'");

		copyFileInfo(privateInfo, publicInfo);
		log.debug("response file info : '" + Json.toJson(publicInfo) + "'");

		return publicInfo;
	}

	private FileInfo getPrivateInfo(String publicIdentifier)
	{
		FileInfo publicInfo = new FileInfo(publicIdentifier, getRootPath(publicIdentifier), true);
		log.debug("request file info : '" + Json.toJson(publicInfo) + "'");

		FileInfo privateInfo = getPrivateInfo(publicInfo);
		log.debug("private file info : '" + Json.toJson(privateInfo) + "'");
		return privateInfo;
	}

	private FileInfo getPrivateInfo(FileInfo publicInfo)
	{
		FileInfo privateInfo = publicFileInfoToPrivate(publicInfo);
		updateInfoFromLocalStore(privateInfo);
		return privateInfo;
	}

	private void copyFileInfo(FileInfo fromInfo, FileInfo toInfo)
	{
		if (fromInfo == null) {
			return;
		}
		toInfo.setExists(fromInfo.exists());
		toInfo.setFolder(fromInfo.isFolder());
		toInfo.setFolder(fromInfo.isFolder());
		toInfo.setLastModified(fromInfo.lastModified());
		toInfo.setLength(fromInfo.getLength());
		if (fromInfo.getFilename() != toInfo.getFilename()) {
			toInfo.setFilename(fromInfo.getFilename());
		}
	}

	private void updateInfoFromLocalStore(FileInfo fileInfo)
	{
		// 暂时直接从磁盘文件获得信息, 以后要把这些信息保存在本地存储中
		File file = new File(fileInfo.getAbsolutePath());
		if (file.exists()) {
			fileInfo.setExists(true);
			fileInfo.setFolder(file.isDirectory());
			fileInfo.setLastModified(file.lastModified());
			fileInfo.setLength(file.length());
		} else {
			fileInfo.setExists(false);
		}
	}

	private FileInfo publicFileInfoToPrivate(FileInfo publicFileInfo)
	{
		if (!publicFileInfo.isPublic()) {
			return publicFileInfo;
		}

		User user = AuthManager.getUser();
		String userRootFolder = user.getRootFolder();
		if (userRootFolder == null) {
			throw new RuntimeException("User [" + user.getName() + "] do not has a storage folder!");
		}

		FileInfo privateInfo = new FileInfo();
		privateInfo.setPublic(false);
		privateInfo.setRootPath(userRootFolder);
		privateInfo.setRelativePath(publicFileInfo.getRelativePath());
		return privateInfo;
	}

	public FileInfo create(String publicIdentifier, FileInfo targetInfo, File targetFile)
	{
		if (publicIdentifier == null) {
			throw Lang.makeThrow("No public file identifier, can not create a file!");
		}

		if (targetInfo == null) {
			throw Lang.makeThrow("No target file information, can not create the file!");
		}

		FileInfo publicInfo = new FileInfo(publicIdentifier, getRootPath(publicIdentifier), true);
		log.debug("request file info : '" + Json.toJson(publicInfo) + "'");

		FileInfo privateInfo = getPrivateInfo(publicInfo);
		log.debug("private file info : '" + Json.toJson(privateInfo) + "'");
		createFile(privateInfo, targetInfo, targetFile);

		copyFileInfo(privateInfo, publicInfo);
		log.debug("response file info : '" + Json.toJson(publicInfo) + "'");

		return publicInfo;
	}

	public FileInfo update(String publicIdentifier, FileInfo targetInfo, File targetFile) throws FileNotFoundException
	{
		if (publicIdentifier == null) {
			throw Lang.makeThrow("No public file identifier, can not update the file!");
		}

		if (targetInfo == null) {
			throw Lang.makeThrow("No target file information, can not update the file!");
		}

		FileInfo publicInfo = new FileInfo(publicIdentifier, getRootPath(publicIdentifier), true);
		log.debug("request file info : '" + Json.toJson(publicInfo) + "'");

		FileInfo privateInfo = getPrivateInfo(publicInfo);
		log.debug("private file info : '" + Json.toJson(privateInfo) + "'");
		updateFile(privateInfo, targetInfo, targetFile);

		copyFileInfo(privateInfo, publicInfo);
		log.debug("response file info : '" + Json.toJson(publicInfo) + "'");

		return publicInfo;
	}

	public FileInfo delete(String publicIdentifier) throws FileNotFoundException
	{
		if (publicIdentifier == null) {
			throw Lang.makeThrow("No public file identifier, can not delete the file!");
		}

		String rootPath = getRootPath(publicIdentifier);
		FileInfo publicInfo = new FileInfo(publicIdentifier, rootPath, true);
		log.debug("request file info : '" + Json.toJson(publicInfo) + "'");

		FileInfo privateInfo = getPrivateInfo(publicInfo);
		log.debug("private file info : '" + Json.toJson(privateInfo) + "'");

		deleteFile(privateInfo);

		FileInfo publicResultInfo = privateFileInfoToPublic(privateInfo);

		return publicResultInfo;
	}

	private String getRootPath(String publicIdentifier)
	{
		String rootPath = FilesInfoResource.PATH;
		if (publicIdentifier.startsWith(rootPath)) {
			return rootPath;
		}
		rootPath = FilesResource.PATH;
		if (publicIdentifier.startsWith(rootPath)) {
			return rootPath;
		}
		throw Lang.makeThrow("'%s' is not a valid public identifier, no match root path!", publicIdentifier);
	}

	private String getRootPath()
	{
		String absolutePath = Mvcs.getReq().getServletPath();
		return getRootPath(absolutePath);
	}

	private FileInfo privateFileInfoToPublic(FileInfo originInfo)
	{
		if (originInfo.isPublic()) {
			return originInfo;
		}
		Mvcs.getReq().getPathInfo();
		String privateRelativePath = originInfo.getRelativePath();
		String publicRootPath = getRootPath();

		FileInfo resultInfo = new FileInfo();
		resultInfo.setPublic(true);
		resultInfo.setRootPath(publicRootPath);
		resultInfo.setRelativePath(privateRelativePath);

		copyFileInfo(originInfo, resultInfo);
		return resultInfo;

	}

	private void updateFile(FileInfo originInfo, FileInfo targetInfo, File targetFile) throws FileNotFoundException
	{
		String privateIdentifer = originInfo.getAbsolutePath();
		File originFile = new File(privateIdentifer);
		if (!originFile.exists()) {
			throw new FileNotFoundException();
		}

		if (targetInfo.getFilename() != originInfo.getFilename()) {
			renameFile(originFile, targetInfo.getFilename());
			originInfo.setFilename(targetInfo.getFilename());
		}

		if (targetFile != null) {
			updateFileContent(originFile, targetFile);
		}

		updateInfoFromLocalStore(originInfo);
	}

	private void createFile(FileInfo privateInfo, FileInfo newFileInfo, File newFile)
	{
		String absolutePath = privateInfo.getAbsolutePath();
		File file = new File(absolutePath);
		if (newFileInfo.isFolder()) {
			file.mkdirs();
		} else {
			try {
				String filePathStr = FileInfo.getPathWithoutFileName(absolutePath);
				File path = new File(filePathStr);
				if (!path.exists()) {
					path.mkdirs();
				}
				file.createNewFile();
				String contentStr = newFileInfo.getContent();
				if (contentStr != null) {
					Files.write(file, contentStr);
				}
				if (newFile != null && newFile.exists()) {
					copyFileContent(newFile, file);
				}
			} catch (IOException e) {
				throw Lang.makeThrow("Create file '%s' failed!", file.getAbsolutePath());
			}
		}
		updateInfoFromLocalStore(privateInfo);
	}

	private void updateFileContent(File oldFile, File newFile) throws FileNotFoundException
	{
		if (!oldFile.exists()) {
			throw new FileNotFoundException();
		}
		try {
			copyFileContent(newFile, oldFile);
		} catch (IOException e) {
			throw Lang.makeThrow("Update file error! " + e.getMessage());
		}
	}

	private void renameFile(File file, String newFilename)
	{
		String oldPath = file.getAbsolutePath();
		file.renameTo(new File(getNewPath(oldPath, newFilename, '\\')));
	}

	private String getNewPath(String oldPath, String newFilename, char splitter)
	{
		int pos = oldPath.lastIndexOf(splitter);
		String newFullPath = oldPath.substring(0, pos + 1) + newFilename;
		return newFullPath;
	}

	private void deleteFile(FileInfo privateInfo) throws FileNotFoundException
	{
		File oldFile = new File(privateInfo.getAbsolutePath());
		if (!oldFile.exists()) {
			throw new FileNotFoundException();
		}
		updateInfoFromLocalStore(privateInfo);
		if (oldFile.delete()) {
			privateInfo.setExists(false);
		} else {
			throw Lang.makeThrow("File '%s' delete failed!", privateInfo.getFilename());
		}
	}

	private static long copyFileContent(File f1, File f2) throws IOException
	{
		long startTime = new Date().getTime();
		int bufferLength = 2097152;
		long fileLength = 0;
		FileInputStream in = new FileInputStream(f1);
		FileOutputStream out = new FileOutputStream(f2);
		byte[] buffer = new byte[bufferLength];
		for (int i = 0; true; i++) {
			int ins = in.read(buffer);
			if (ins == -1) {
				in.close();
				out.flush();
				out.close();
				log.debug("It took " + (new Date().getTime() - startTime) + "ms to copy file '" + f2.getAbsolutePath()
						+ "'");
				return fileLength;
			} else
				out.write(buffer, 0, ins);
			fileLength += ins;
		}
	}

	public File getPrivateFile(String publicIdentifier) throws FileNotFoundException
	{
		FileInfo privateInfo = getPrivateInfo(publicIdentifier);
		File file = new File(privateInfo.getAbsolutePath());
		if (!file.exists()) {
			throw new FileNotFoundException();
		}
		return file;
	}
}
